Repository navigation
Harden SDK hook context and capability diagnostics - #105
Conversation
Implement pm-e9ut and GitHub #103/#104 by enriching item-related onWrite hook contexts with item identity, before/after snapshots, and changed fields while preserving minimal path/scope/op events for non-item writes. Add structured activation traces and doctor remediation for missing manifest capabilities, plus first-party manifest/API capability guard coverage and package-first dogfood assertions for activation/blocking/capability failures. Update commander to 15.0.0, refresh pm-changelog to 2026.6.3, regenerate CHANGELOG.md, and record tracker evidence. Verification: pnpm build; node scripts/run-tests.mjs test -- tests/unit/contracts-command.spec.ts tests/unit/help-runtime.spec.ts tests/unit/config-positional-value.spec.ts tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.ts; node scripts/run-tests.mjs test -- tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.ts; npm run dogfood:package-first; pnpm audit --audit-level moderate && pnpm outdated --format json; pnpm security:scan; pnpm changelog:pm:check.
|
@codex review Please review the SDK hook context and manifest capability guardrail changes, especially compatibility of optional onWrite payload fields, package doctor diagnostics, and package-first dogfood gating. |
|
Warning Review limit reached
More reviews will be available in 8 minutes and 54 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (16)
📒 Files selected for processing (23)
📝 WalkthroughWalkthroughEnriches the ChangesHook context enrichment and manifest capability validation
pm-changelog extension version bump
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes The PR introduces two coordinated features affecting the extension contract, runtime validation, and multiple mutation call sites. The capability validation logic is straightforward but requires understanding the activation flow, trace payload, and doctor warning classification. The hook context enrichment involves coordinated changes across four mutation entry points (create, restore, mutateItem/deleteItem, import) with careful ordering (early changedFields computation) and document before/after handling. Tests validate both features concretely, but the scope spans type contracts, validation infrastructure, multiple call sites, and documentation. Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request enriches the SDK extension hook context by passing item identity and before/after snapshots to onWrite hooks for item mutations (create, update, delete, restore, and import). It also introduces structured missing-capability activation traces and corresponding doctor diagnostics/remediations to catch manifest capability omissions before publishing. Additionally, the commander dependency is updated to ^15.0.0, and several new opt-in CLI flags are added to pm-changelog (such as --section-by, --conventional, --contributors, --limit, --since-version, and --changelog-json). Review feedback recommends using optional chaining when accessing metadata properties on document objects (such as afterDocument, beforeDocument, restoredDocument, and document) across various command and store files to prevent potential runtime TypeErrors if these documents are undefined or null.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if afterDocument or its metadata is undefined or null (e.g., in testing environments or edge cases), use optional chaining when accessing these properties.
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| item_id: afterDocument?.metadata?.id, | |
| item_type: afterDocument?.metadata?.type, |
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if afterDocument or its metadata is undefined or null (e.g., in testing environments or edge cases), use optional chaining when accessing these properties.
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| item_id: afterDocument?.metadata?.id, | |
| item_type: afterDocument?.metadata?.type, |
| item_id: restoredDocument.metadata.id, | ||
| item_type: restoredDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if restoredDocument or its metadata is undefined or null, use optional chaining when accessing these properties.
| item_id: restoredDocument.metadata.id, | |
| item_type: restoredDocument.metadata.type, | |
| item_id: restoredDocument?.metadata?.id, | |
| item_type: restoredDocument?.metadata?.type, |
| item_id: restoredDocument.metadata.id, | ||
| item_type: restoredDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if restoredDocument or its metadata is undefined or null, use optional chaining when accessing these properties.
| item_id: restoredDocument.metadata.id, | |
| item_type: restoredDocument.metadata.type, | |
| item_id: restoredDocument?.metadata?.id, | |
| item_type: restoredDocument?.metadata?.type, |
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if afterDocument or its metadata is undefined or null, use optional chaining when accessing these properties.
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| item_id: afterDocument?.metadata?.id, | |
| item_type: afterDocument?.metadata?.type, |
| item_id: beforeDocument.metadata.id, | ||
| item_type: beforeDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if beforeDocument or its metadata is undefined or null, use optional chaining when accessing these properties.
| item_id: beforeDocument.metadata.id, | |
| item_type: beforeDocument.metadata.type, | |
| item_id: beforeDocument?.metadata?.id, | |
| item_type: beforeDocument?.metadata?.type, |
| item_id: beforeDocument.metadata.id, | ||
| item_type: beforeDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if beforeDocument or its metadata is undefined or null, use optional chaining when accessing these properties.
| item_id: beforeDocument.metadata.id, | |
| item_type: beforeDocument.metadata.type, | |
| item_id: beforeDocument?.metadata?.id, | |
| item_type: beforeDocument?.metadata?.type, |
| item_id: document.metadata.id, | ||
| item_type: document.metadata.type, |
There was a problem hiding this comment.
| item_id: document.metadata.id, | ||
| item_type: document.metadata.type, |
There was a problem hiding this comment.
| for (const failure of activationFailures) { | ||
| const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability; | ||
| if (typeof missingCapability === "string" && missingCapability.trim().length > 0) { | ||
| warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if failure or failure.trace is undefined or null, use optional chaining when accessing these properties.
| for (const failure of activationFailures) { | |
| const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability; | |
| if (typeof missingCapability === "string" && missingCapability.trim().length > 0) { | |
| warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`); | |
| } | |
| } | |
| for (const failure of activationFailures) { | |
| const missingCapability = failure?.trace?.missing_capability ?? failure?.trace?.capability; | |
| if (failure?.name && typeof missingCapability === "string" && missingCapability.trim().length > 0) { | |
| warnings.push("extension_capability_missing:" + failure.name + ":" + missingCapability.trim().toLowerCase()); | |
| } | |
| } |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/unit/package-manifest.spec.ts`:
- Around line 547-548: The current Promise.all mapping swallows all I/O errors
by using .catch(() => ""), so change the catch on the readFile promise (the
async map over sourcePaths using readFile(sourcePath, "utf8")) to only return ""
when the error.code === "ENOENT" and rethrow any other errors; locate the
readFile(...) .catch(...) used inside the Promise.all over sourcePaths and
update the catch handler accordingly to inspect err.code and rethrow non-ENOENT
errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 3db26f3c-dc67-4411-a67b-f0dca41efd11
⛔ Files ignored due to path filters (17)
.agents/pm/extensions/pm-changelog/dist/cli.jsis excluded by!**/dist/**.agents/pm/extensions/pm-changelog/dist/cli.js.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/extension.d.ts.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/extension.jsis excluded by!**/dist/**.agents/pm/extensions/pm-changelog/dist/extension.js.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/generator.d.tsis excluded by!**/dist/**.agents/pm/extensions/pm-changelog/dist/generator.d.ts.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/generator.jsis excluded by!**/dist/**.agents/pm/extensions/pm-changelog/dist/generator.js.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/index.d.tsis excluded by!**/dist/**.agents/pm/extensions/pm-changelog/dist/index.d.ts.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/index.jsis excluded by!**/dist/**.agents/pm/extensions/pm-changelog/dist/index.js.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/types.d.tsis excluded by!**/dist/**.agents/pm/extensions/pm-changelog/dist/types.d.ts.mapis excluded by!**/dist/**,!**/*.map.agents/pm/extensions/pm-changelog/dist/types.js.mapis excluded by!**/dist/**,!**/*.mappnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (24)
.agents/pm/extensions/.managed-extensions.json.agents/pm/extensions/pm-changelog/CHANGELOG.md.agents/pm/extensions/pm-changelog/README.md.agents/pm/extensions/pm-changelog/docs/usage.md.agents/pm/extensions/pm-changelog/manifest.json.agents/pm/extensions/pm-changelog/package.json.agents/pm/history/pm-e9ut.jsonl.agents/pm/tasks/pm-e9ut.toonCHANGELOG.mddocs/EXTENSIONS.mddocs/SDK.mdpackage.jsonscripts/dogfood-package-first.mjssrc/cli/commands/create.tssrc/cli/commands/extension.tssrc/cli/commands/extension/doctor.tssrc/cli/commands/restore.tssrc/core/extensions/extension-types.tssrc/core/extensions/loader.tssrc/core/store/item-store.tssrc/sdk/package-import-adapters.tstests/unit/extension-command.spec.tstests/unit/item-store.spec.tstests/unit/package-manifest.spec.ts
There was a problem hiding this comment.
Code Review
This pull request updates the pm-changelog extension to version 2026.6.3, introducing several opt-in enhancements such as custom section grouping, Conventional-Commits headings, contributor lists, release windowing, and structured JSON output. It also enriches the onWrite hook context with optional item-aware metadata (such as item ID, type, and before/after snapshots) and improves extension activation diagnostics by tracing and reporting missing manifest capabilities. Additionally, the commander dependency is upgraded to ^15.0.0. The review feedback highlights several robustness improvements, including adding defensive checks and optional chaining to prevent potential runtime TypeError crashes when handling item mutations, create fields, or activation failures, as well as expanding the static capability usage test to scan all source files recursively.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| path: effectiveTargetItemPath, | ||
| scope: "project", | ||
| op: params.op, | ||
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, | ||
| before: beforeDocument, | ||
| after: afterDocument, | ||
| changed_fields: mutation.changedFields, | ||
| })), | ||
| ...(await runActiveOnWriteHooks({ | ||
| path: historyPath, | ||
| scope: "project", | ||
| op: `${params.op}:history`, | ||
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, | ||
| before: beforeDocument, | ||
| after: afterDocument, | ||
| changed_fields: mutation.changedFields, | ||
| })), |
There was a problem hiding this comment.
If the mutate function passed to mutateItem does not return an object (e.g., if it returns void or undefined after mutating the document in-place), mutation will be undefined. Accessing mutation.changedFields will then throw a TypeError at runtime. Using optional chaining (mutation?.changedFields) prevents this potential crash.
| path: effectiveTargetItemPath, | |
| scope: "project", | |
| op: params.op, | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: beforeDocument, | |
| after: afterDocument, | |
| changed_fields: mutation.changedFields, | |
| })), | |
| ...(await runActiveOnWriteHooks({ | |
| path: historyPath, | |
| scope: "project", | |
| op: `${params.op}:history`, | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: beforeDocument, | |
| after: afterDocument, | |
| changed_fields: mutation.changedFields, | |
| })), | |
| path: effectiveTargetItemPath, | |
| scope: "project", | |
| op: params.op, | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: beforeDocument, | |
| after: afterDocument, | |
| changed_fields: mutation?.changedFields, | |
| })), | |
| ...(await runActiveOnWriteHooks({ | |
| path: historyPath, | |
| scope: "project", | |
| op: params.op + ":history", | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: beforeDocument, | |
| after: afterDocument, | |
| changed_fields: mutation?.changedFields, | |
| })), |
| const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [ | ||
| ...Object.keys(registeredItemFieldValues), | ||
| ...Object.keys(runtimeCreateFieldValues.values), | ||
| ]); |
There was a problem hiding this comment.
If registeredItemFieldValues or runtimeCreateFieldValues (or runtimeCreateFieldValues.values) is undefined or null, calling Object.keys or accessing .values will throw a TypeError at runtime. Adding nullish coalescing and optional chaining makes this robust against missing or uninitialized field values.
| const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [ | |
| ...Object.keys(registeredItemFieldValues), | |
| ...Object.keys(runtimeCreateFieldValues.values), | |
| ]); | |
| const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [ | |
| ...Object.keys(registeredItemFieldValues ?? {}), | |
| ...Object.keys(runtimeCreateFieldValues?.values ?? {}), | |
| ]); |
| export function classifyDoctorActivationFailureWarnings( | ||
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>, | ||
| ): string[] { | ||
| const warnings: string[] = []; | ||
| for (const failure of activationFailures) { | ||
| const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability; | ||
| if (typeof missingCapability === "string" && missingCapability.trim().length > 0) { | ||
| warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`); | ||
| } | ||
| } | ||
| return [...new Set(warnings)].sort((left, right) => left.localeCompare(right)); | ||
| } |
There was a problem hiding this comment.
If activationFailures is null or undefined (e.g., if the activation result is structured differently or missing the field), iterating over it will throw a TypeError. Adding a default parameter or a defensive check ensures the doctor command remains resilient.
| export function classifyDoctorActivationFailureWarnings( | |
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>, | |
| ): string[] { | |
| const warnings: string[] = []; | |
| for (const failure of activationFailures) { | |
| const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability; | |
| if (typeof missingCapability === "string" && missingCapability.trim().length > 0) { | |
| warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`); | |
| } | |
| } | |
| return [...new Set(warnings)].sort((left, right) => left.localeCompare(right)); | |
| } | |
| export function classifyDoctorActivationFailureWarnings( | |
| activationFailures?: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>, | |
| ): string[] { | |
| const warnings: string[] = []; | |
| if (!activationFailures) return warnings; | |
| for (const failure of activationFailures) { | |
| const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability; | |
| if (typeof missingCapability === "string" && missingCapability.trim().length > 0) { | |
| warnings.push("extension_capability_missing:" + failure.name + ":" + missingCapability.trim().toLowerCase()); | |
| } | |
| } | |
| return [...new Set(warnings)].sort((left, right) => left.localeCompare(right)); | |
| } |
| const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")]; | ||
| const source = ( | ||
| await Promise.all(sourcePaths.map(async (sourcePath) => readFile(sourcePath, "utf8").catch(() => ""))) | ||
| ).join("\n"); |
There was a problem hiding this comment.
Scanning only index.ts and index.js at the root of the extension directory will miss API registrations that are modularized into other source files (e.g., src/commands.ts or src/hooks.ts) and imported. To make this test robust and prevent false negatives, consider scanning all source files recursively using a glob pattern or a recursive directory reader.
| const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")]; | |
| const source = ( | |
| await Promise.all(sourcePaths.map(async (sourcePath) => readFile(sourcePath, "utf8").catch(() => ""))) | |
| ).join("\n"); | |
| const glob = (await import("fast-glob")).default; | |
| const sourcePaths = await glob("**/*.{ts,js}", { cwd: extensionDirectory, absolute: true, ignore: ["**/node_modules/**", "**/dist/**"] }); | |
| const source = ( | |
| await Promise.all(sourcePaths.map(async (sourcePath) => readFile(sourcePath, "utf8").catch(() => ""))) | |
| ).join("\n"); |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1d0f6eaef
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "@sentry/node": "^10.56.0", | ||
| "@toon-format/toon": "^2.3.0", | ||
| "commander": "^14.0.3", | ||
| "commander": "^15.0.0", |
There was a problem hiding this comment.
Keep commander compatible with Node 20
This dependency bump makes installs incompatible with the package's advertised Node support: package.json still declares engines.node as >=20, but the resolved commander@15.0.0 entry in pnpm-lock.yaml requires Node >=22.12.0. In Node 20 environments, package managers running with engine enforcement can reject the install, and users following the documented Node 20 support path will be blocked before any of the SDK/package doctor checks can run.
Useful? React with 👍 / 👎.
| item_type: restoredDocument.metadata.type, | ||
| before: resolvedCurrentDocument, | ||
| after: restoredDocument, | ||
| changed_fields: ["restored"], |
There was a problem hiding this comment.
Report actual restore fields to onWrite hooks
For restores that change real item metadata/body fields, the new SDK payload tells onWrite hooks only changed_fields: ["restored"], while the command result below computes the actual diff with changedFields(resolvedCurrentDocument, restoredDocument). Sync/audit packages using the new optional payload to mirror exact item changes will miss fields like status, title, or deadline after a restore; pass the same computed changed field list into both restore write hooks instead of the sentinel.
Useful? React with 👍 / 👎.
|
@codex review Follow-up commit a6088f1 addresses the actionable CodeRabbit finding by only swallowing ENOENT when probing optional first-party extension source files. Please re-review the updated PR for any remaining SDK hook context, manifest capability, package doctor, and changelog/dogfood issues. |
There was a problem hiding this comment.
Code Review
This pull request introduces several opt-in enhancements to the pm-changelog extension, including structured JSON output, custom grouping options, conventional commit headings, contributor lists, and history windowing. It also enriches the onWrite hook context with item-specific metadata and snapshots across creation, mutation, deletion, and restoration paths, while improving capability validation in the SDK. A critical issue was identified in src/cli/commands/create.ts where the undefined variable beforeDocument is referenced, which would cause a runtime ReferenceError during item creation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| path: itemPath, | ||
| scope: "project", | ||
| op: "create", | ||
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, | ||
| before: beforeDocument, | ||
| after: afterDocument, | ||
| changed_fields: changedFields, | ||
| })), | ||
| ...(await runActiveOnWriteHooks({ | ||
| path: historyPath, | ||
| scope: "project", | ||
| op: "create:history", | ||
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, | ||
| before: beforeDocument, | ||
| after: afterDocument, | ||
| changed_fields: changedFields, | ||
| })), |
There was a problem hiding this comment.
The variable beforeDocument is referenced on lines 1915 and 1925, but it is not defined anywhere in create.ts. This will cause a runtime ReferenceError when executing the create command with active hooks. Since this is a new item creation, there is no previous state, so before should be set to undefined.
| path: itemPath, | |
| scope: "project", | |
| op: "create", | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: beforeDocument, | |
| after: afterDocument, | |
| changed_fields: changedFields, | |
| })), | |
| ...(await runActiveOnWriteHooks({ | |
| path: historyPath, | |
| scope: "project", | |
| op: "create:history", | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: beforeDocument, | |
| after: afterDocument, | |
| changed_fields: changedFields, | |
| })), | |
| path: itemPath, | |
| scope: "project", | |
| op: "create", | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: undefined, | |
| after: afterDocument, | |
| changed_fields: changedFields, | |
| })), | |
| ...(await runActiveOnWriteHooks({ | |
| path: historyPath, | |
| scope: "project", | |
| op: "create:history", | |
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| before: undefined, | |
| after: afterDocument, | |
| changed_fields: changedFields, | |
| })), |
🧩 Analysis chainI'll pull the key changed files to do a thorough re-review. 🏁 Script executed: Unrecoverable error: Failed to clone repository 🏁 Script executed: Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream
✅ What looks goodSDK hook context (
|
| Area | Status |
|---|---|
OnWriteHookContext optional fields / backward compat |
✅ |
create.ts ordering fix |
✅ |
loader.ts structured capability error |
✅ |
classifyDoctorActivationFailureWarnings |
✅ |
| Dogfood assertions | ✅ |
| ENOENT fix (a6088f1) | ✅ |
| Commander 15 Node ≥ 22.12.0 + ESM-only | |
registration_index: -1 sentinel |
|
Import changed_fields value |
|
package-manifest.spec.ts coverage scope |
📝 add comment |
Synthetic changed_fields sentinels |
📝 add to SDK.md |
🧠 Learnings used
Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.
✅ Actions performed
Full review triggered.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6088f13e1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (limit !== undefined && result.length > limit) { | ||
| result = result.slice(0, limit); |
There was a problem hiding this comment.
Exclude Unreleased when applying the release limit
When --all-release-tags has any post-latest-tag items, candidateSections starts with the Unreleased bucket, so this slice(0, limit) makes --limit 10 keep Unreleased plus only nine tagged releases. The CLI help and usage docs describe the flag as keeping the most recent N release sections/history releases, while the adjacent comment says Unreleased is always kept, so CI users trimming long histories will silently drop one requested tagged release whenever there is an Unreleased section.
Useful? React with 👍 / 👎.
| throw createRegistrationValidationError( | ||
| `${method} requires capability '${capability}' in extension manifest capabilities`, | ||
| { | ||
| method, | ||
| registration_index: -1, | ||
| capability, | ||
| missing_capability: capability, |
There was a problem hiding this comment.
Preserve missing capability traces through command flag validation
When an extension declares only commands and calls api.registerCommand({ ..., flags: [...] }), the missing schema capability is raised here from inside registerCommand's metadata try block, but that catch wraps the error with a new registerCommand trace that drops missing_capability. In that scenario pm package doctor --detail deep --trace reports a generic activation failure instead of the new extension_capability_missing triage/remediation, so command-definition flags remain an un-diagnosed manifest capability gap.
Useful? React with 👍 / 👎.
| lines.push("### Contributors", ""); | ||
| lines.push(names.map((name) => `@${name}`).join(", ")); |
There was a problem hiding this comment.
Avoid double-prefixing contributor handles
When --contributors is used and an item's assignee or author is already stored as a handle like @alice, this formatter unconditionally prepends another @, producing @@alice; mixed alice/@alice values also bypass the de-dupe key and can appear twice. Since pm authors often use stable handles, normalizing a leading @ before de-duping/formatting would keep the generated Contributors section valid.
Useful? React with 👍 / 👎.
|
@codex review Follow-up commit 0077666 addresses the latest review pass: commander is reverted to 14.0.3 to preserve package.json Node >=20 and CI Node 20 compatibility, SDK docs now call out lifecycle changed_fields sentinels, registration_index=-1 is documented as a pre-registration trace sentinel, and the static manifest guard is annotated as heuristic. Please re-review for any remaining actionable issues. |
🧩 Analysis chainI'll pull the current state of the files touched by 0077666 to verify the fixes. 🏁 Script executed: Unrecoverable error: Failed to clone repository 🏁 Script executed: Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream
Re-review after commit 0077666Previously flagged findings — claimed addressed
All five items were substantive and the described fixes are appropriate. No concerns about the fix approach for any of them. One remaining low-priority flag (new)
The AI summary notes that a single Since Suggestion (low priority): Shallow-copy the reference at each call site, or freeze the object before passing: before: Object.freeze({ ...beforeDocument }),or accept it as-is and document in Everything elseAll other areas — the structured capability error in The PR is in good shape. The shared-reference issue above is the only new item, and it is low-priority / best addressed with a documentation note if not a code change. 🧠 Learnings used✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Code Review
This pull request introduces several opt-in enhancements to the pm-changelog extension, including within-release grouping by type, status, or label, Conventional-Commits heading mapping, per-release contributor lists, release window limiting, and structured JSON document output. It also enriches the onWrite hook context with item identity and snapshots, and improves missing manifest capability diagnostics in pm package doctor with structured activation traces. The review feedback points out a potential issue in generator.js where duplicate tags in item.tags can lead to duplicate items under the same tag heading when grouping by label, and suggests deduplicating the tags using a Set before iteration.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| push(titleCase(typeof item.status === "string" && item.status.trim() ? item.status.trim() : "Unknown"), item); | ||
| } | ||
| else { | ||
| const tags = (item.tags ?? []).map((tag) => tag.trim()).filter(Boolean); |
There was a problem hiding this comment.
If item.tags contains duplicate tags (e.g., ['bug', 'bug']), the current implementation will iterate over the duplicates and push the item multiple times into the same group. Deduplicating the tags using a Set before iteration prevents duplicate items under the same tag heading.
| const tags = (item.tags ?? []).map((tag) => tag.trim()).filter(Boolean); | |
| const tags = [...new Set((item.tags ?? []).map((tag) => tag.trim()).filter(Boolean))]; |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 007766649a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (limit !== undefined && result.length > limit) { | ||
| result = result.slice(0, limit); |
There was a problem hiding this comment.
Exclude Unreleased from --limit slicing
When --all-release-tags is used, resolveReleaseTagWindows prepends an Unreleased window, and this code slices the full result array including that sentinel. In the common --all-release-tags --limit 1 case, the output contains only Unreleased and drops the most recent tagged release, even though the new docs/comment say Unreleased is always kept and the limit applies to release sections. Split out the undefined-version sentinel before applying the limit so the newest N actual releases still render.
Useful? React with 👍 / 👎.
| function parseLimit(value) { | ||
| const parsed = Number.parseInt(value, 10); |
There was a problem hiding this comment.
Reject malformed --limit values
For inputs like --limit 1.5 or --limit 1abc, Number.parseInt returns 1, so the new flag silently accepts a malformed value and renders a different release count instead of honoring the documented positive-integer contract. Parse the whole string (for example with Number(...) plus Number.isInteger) so CI/config typos fail fast rather than truncating.
Useful? React with 👍 / 👎.
| const names = collectContributors(section.items); | ||
| if (names.length > 0) { | ||
| lines.push("### Contributors", ""); | ||
| lines.push(names.map((name) => `@${name}`).join(", ")); |
There was a problem hiding this comment.
Avoid double-prefixing contributor handles
When an item's assignee or author is already stored as a GitHub-style handle such as @alice, enabling --contributors renders it as @@alice here, and alice/@alice are also treated as separate contributors by the de-dupe key. Normalize away one leading @ before de-duping/rendering so existing handle-form values do not produce broken contributor entries.
Useful? React with 👍 / 👎.
|
@codex review Follow-up commit 9705c2a records the package-owned review follow-up only. The latest Gemini finding was verified and fixed upstream in pm-changelog PR #25 (unbraind/pm-changelog#25) under tracked item pmc-an10; this pm-cli commit only documents that evidence in pm-e9ut. Please re-review the final pm-cli PR head for any remaining actionable issues. |
🧩 Analysis chain🏁 Script executed: Unrecoverable error: Failed to clone repository 🏁 Script executed: Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream
Final re-review after commit 9705c2aCommit 9705c2a — nothing to flagThis commit is documentation-only: it appends entries to Status of all previously raised findings
One remaining open itemShared A single The fix is either defensive: before: { ...beforeDocument }, // shallow copy at each call siteor contractual (add a note to This does not block the PR. Would you like me to open a follow-up issue to track it? Overall assessmentThe PR is in good shape. All six substantive findings from the previous passes are resolved. The one remaining item is low-priority and does not affect correctness for well-behaved hook implementations. The SDK hook context enrichment, structured capability guardrails, doctor diagnostics, and dogfood gating are all solid. ✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Code Review
This pull request introduces several opt-in enhancements to the pm-changelog extension, including within-release grouping (--section-by), Conventional-Commits headings (--conventional), per-release contributor lists (--contributors), history windowing (--limit and --since-version), and structured JSON output (--changelog-json). Additionally, it enriches the SDK's onWrite hook context with item identity and content snapshots, and implements manifest capability guardrails that raise actionable diagnostics (extension_capability_missing) in pm package doctor when registration APIs are called without declaring the required capabilities. Feedback on the changes suggests adding a guard in classifyDoctorActivationFailureWarnings to prevent runtime crashes if activationFailures is null or undefined, using optional chaining (?.) when accessing document.metadata in package-import-adapters.ts to safely handle malformed or missing metadata, and expanding the sourcePaths in the package manifest unit test to scan standard src/ entry points for first-party extensions.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export function classifyDoctorActivationFailureWarnings( | ||
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>, | ||
| ): string[] { | ||
| const warnings: string[] = []; | ||
| for (const failure of activationFailures) { |
There was a problem hiding this comment.
To prevent potential runtime crashes (such as TypeError: activationFailures is not iterable) when activationFailures is null or undefined, we should add a guard to classifyDoctorActivationFailureWarnings.
export function classifyDoctorActivationFailureWarnings(
activationFailures?: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> | null,
): string[] {
const warnings: string[] = [];
if (!activationFailures) {
return warnings;
}
for (const failure of activationFailures) {| item_id: document.metadata.id, | ||
| item_type: document.metadata.type, |
There was a problem hiding this comment.
Since document is passed from external package importers, it is safer to use optional chaining (?.) when accessing document.metadata to prevent potential TypeErrors if the imported document is malformed or missing metadata.
| item_id: document.metadata.id, | |
| item_type: document.metadata.type, | |
| item_id: document?.metadata?.id, | |
| item_type: document?.metadata?.type, |
| item_id: document.metadata.id, | ||
| item_type: document.metadata.type, |
| // Heuristic guard: catches direct index.ts/index.js registrations, while package doctor remains the runtime check. | ||
| const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")]; |
There was a problem hiding this comment.
First-party extensions (such as pm-changelog itself) typically organize their source code under a src/ directory (e.g., src/extension.ts or src/index.ts). To ensure the heuristic capability alignment check actually scans these source files, we should expand sourcePaths to include standard src/ entry points.
// Heuristic guard: catches direct index.ts/index.js registrations, while package doctor remains the runtime check.
const sourcePaths = [
path.join(extensionDirectory, "index.ts"),
path.join(extensionDirectory, "index.js"),
path.join(extensionDirectory, "src", "index.ts"),
path.join(extensionDirectory, "src", "extension.ts"),
];|
@codex review Follow-up commit 3fc9634 redacts the host-specific path from pm evidence after |
🧩 Analysis chain🏁 Script executed: Unrecoverable error: Failed to clone repository
Final re-review after commit 3fc9634Commit 3fc9634 — clean, nothing to flagThis commit is a narrow tracker-evidence redaction: a host-specific local filesystem path (captured during local verification runs) was present in Status of all findings across the full review chain
One remaining low-priority itemThe shared Would you like me to open a follow-up GitHub issue to track it? It's clearly out of scope for this PR given everything else is resolved. OverallThe PR is in excellent shape. All seven substantive findings from the review chain are resolved, and 🧠 Learnings used✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Code Review
This pull request enriches the onWrite hook context with item identity and snapshots during item mutations (create, update, delete, restore, and import) and updates the extension loader to throw structured registration validation errors when required capabilities are missing from the manifest. It also introduces several opt-in enhancements to pm-changelog, such as structured JSON output, release windowing, contributor lists, and custom section grouping. Feedback on the changes suggests expanding the source paths checked by the first-party manifest capability alignment test to include src/ directories (e.g., src/index.ts and src/extension.ts) to prevent false negatives on extensions like pm-changelog itself.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as { capabilities?: unknown }; | ||
| const declaredCapabilities = new Set(Array.isArray(manifest.capabilities) ? manifest.capabilities : []); | ||
| // Heuristic guard: catches direct index.ts/index.js registrations, while package doctor remains the runtime check. | ||
| const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")]; |
There was a problem hiding this comment.
The heuristic guard currently only checks index.ts and index.js in the root of the extension directory. However, first-party extensions (such as pm-changelog itself) often place their source code in a src/ directory (e.g., src/extension.ts or src/index.ts). As a result, this test will completely miss registration calls in those extensions, leading to false negatives.
Expanding sourcePaths to include common first-party source locations like src/index.ts and src/extension.ts ensures the alignment check is actually executed for these extensions.
const sourcePaths = [
path.join(extensionDirectory, "index.ts"),
path.join(extensionDirectory, "index.js"),
path.join(extensionDirectory, "src/index.ts"),
path.join(extensionDirectory, "src/extension.ts"),
];There was a problem hiding this comment.
Code Review
This pull request updates the pm-changelog extension to version 2026.6.3 and introduces several opt-in enhancements, including within-release grouping, conventional headings, contributor lists, history limiting, and structured JSON output. Additionally, it enriches the SDK's onWrite hook context with optional item-specific metadata during mutations, improves extension activation diagnostics by tracing missing manifest capabilities, adds a static capability alignment test, and reverts commander to 14.0.3 for Node 20 compatibility. The review feedback suggests defensively using optional chaining when accessing metadata properties on document objects across multiple files, and defaulting the activationFailures parameter to an empty array in classifyDoctorActivationFailureWarnings to prevent potential runtime TypeErrors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime crashes (TypeErrors) if afterDocument or its metadata is undefined, use optional chaining when accessing id and type.
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| item_id: afterDocument?.metadata?.id, | |
| item_type: afterDocument?.metadata?.type, |
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime crashes (TypeErrors) if afterDocument or its metadata is undefined, use optional chaining when accessing id and type.
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| item_id: afterDocument?.metadata?.id, | |
| item_type: afterDocument?.metadata?.type, |
| item_id: restoredDocument.metadata.id, | ||
| item_type: restoredDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime crashes (TypeErrors) if restoredDocument or its metadata is undefined, use optional chaining when accessing id and type.
| item_id: restoredDocument.metadata.id, | |
| item_type: restoredDocument.metadata.type, | |
| item_id: restoredDocument?.metadata?.id, | |
| item_type: restoredDocument?.metadata?.type, |
| item_id: restoredDocument.metadata.id, | ||
| item_type: restoredDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime crashes (TypeErrors) if restoredDocument or its metadata is undefined, use optional chaining when accessing id and type.
| item_id: restoredDocument.metadata.id, | |
| item_type: restoredDocument.metadata.type, | |
| item_id: restoredDocument?.metadata?.id, | |
| item_type: restoredDocument?.metadata?.type, |
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime crashes (TypeErrors) if afterDocument or its metadata is undefined, use optional chaining when accessing id and type.
| item_id: afterDocument.metadata.id, | |
| item_type: afterDocument.metadata.type, | |
| item_id: afterDocument?.metadata?.id, | |
| item_type: afterDocument?.metadata?.type, |
| item_id: beforeDocument.metadata.id, | ||
| item_type: beforeDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime crashes (TypeErrors) if beforeDocument or its metadata is undefined, use optional chaining when accessing id and type.
| item_id: beforeDocument.metadata.id, | |
| item_type: beforeDocument.metadata.type, | |
| item_id: beforeDocument?.metadata?.id, | |
| item_type: beforeDocument?.metadata?.type, |
| item_id: beforeDocument.metadata.id, | ||
| item_type: beforeDocument.metadata.type, |
There was a problem hiding this comment.
To prevent potential runtime crashes (TypeErrors) if beforeDocument or its metadata is undefined, use optional chaining when accessing id and type.
| item_id: beforeDocument.metadata.id, | |
| item_type: beforeDocument.metadata.type, | |
| item_id: beforeDocument?.metadata?.id, | |
| item_type: beforeDocument?.metadata?.type, |
| item_id: document.metadata.id, | ||
| item_type: document.metadata.type, |
There was a problem hiding this comment.
| item_id: document.metadata.id, | ||
| item_type: document.metadata.type, |
There was a problem hiding this comment.
| export function classifyDoctorActivationFailureWarnings( | ||
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>, | ||
| ): string[] { |
There was a problem hiding this comment.
To prevent potential runtime TypeErrors if activationFailures is undefined or null, default the parameter to an empty array.
| export function classifyDoctorActivationFailureWarnings( | |
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>, | |
| ): string[] { | |
| export function classifyDoctorActivationFailureWarnings( | |
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [], | |
| ): string[] { |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fc9634459
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }); | ||
| } | ||
| if (limit !== undefined && result.length > limit) { | ||
| result = result.slice(0, limit); |
There was a problem hiding this comment.
Exclude Unreleased from --limit slicing
When --all-release-tags produces a non-empty Unreleased bucket, resolveReleaseTagWindows prepends it before tagged releases, but this slice applies --limit to the whole array. As a result, --all-release-tags --limit 1 returns only Unreleased and drops the newest tagged release, and --limit N yields only N-1 actual releases whenever Unreleased is present, contrary to the new docs/contract that limit keeps the newest N releases while Unreleased is always kept. Keep the Unreleased section outside the counted slice.
Useful? React with 👍 / 👎.
| item_id: afterDocument.metadata.id, | ||
| item_type: afterDocument.metadata.type, | ||
| before: beforeDocument, | ||
| after: afterDocument, |
There was a problem hiding this comment.
Report the post-override item snapshot to write hooks
In repositories with an item_store_write service override that changes contents or uses skip_write, this new hook context still publishes the pre-override afterDocument even though line 459 writes effectiveSerializedAfter (or writes nothing). A sync/audit extension consuming the new after snapshot can therefore mirror an item state that was not actually written at effectiveTargetItemPath; parse the effective contents for the hook snapshot or omit after when the override makes it unreliable.
Useful? React with 👍 / 👎.
| // OPT-IN (`--changelog-json`): structured document; never writes a file. | ||
| if (booleanOption(ctx.options, "changelog-json", "changelogJson")) { | ||
| const document = buildChangelogDocument(generationOptions); | ||
| return { document, format: "json", item_count: document.item_count }; |
There was a problem hiding this comment.
Emit raw JSON for the extension changelog-json flag
For pm changelog generate --changelog-json without the global --json flag, returning this object still goes through pm's default TOON renderer; the format: "json" field is just payload data and does not switch output formatting. The advertised extension command therefore prints a TOON wrapper around document instead of a JSON changelog document, unlike the standalone CLI path. Return native JSON/mark the command result for JSON output, or require and document the global --json flag.
Useful? React with 👍 / 👎.
|
@codex review Follow-up commit 4555cc8 addresses the latest actionable review suggestions: the doctor activation failure classifier now defaults to an empty array, and the first-party manifest capability heuristic also checks common src/index.* and src/extension.* entry files. Local verification passed focused tests, build, and the full static job sequence. Please re-review the final head for remaining actionable issues. |
🧩 Analysis chain🏁 Script executed: Unrecoverable error: Failed to clone repository 🏁 Script executed: Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream
Re-review after commit 4555cc8Claimed fix 1 —
|
| Finding | Addressed in | Status |
|---|---|---|
| ENOENT-only swallowing in manifest guard | a6088f13 |
✅ |
| Commander 15 → Node ≥22 / ESM-only breakage | 00776664 |
✅ |
registration_index: -1 sentinel undocumented |
00776664 |
✅ |
Import changed_fields value undocumented |
00776664 |
✅ |
Synthetic "restored" / "deleted" sentinels undocumented |
00776664 |
✅ |
package-manifest.spec.ts heuristic scope (comment) |
00776664 |
✅ |
| Host-specific path in pm evidence | 3fc96344 |
✅ |
classifyDoctorActivationFailureWarnings empty-array default |
4555cc8b |
✅ |
Heuristic extended to src/index.* / src/extension.* |
4555cc8b |
✅ |
Shared beforeDocument ref in package-import-adapters.ts |
— |
One remaining low-priority item
The shared emptyImportedDocument() reference passed as before to both runActiveOnWriteHooks invocations in src/sdk/package-import-adapters.ts remains the only open item. A well-behaved hook must not mutate context.before, but OnWriteHookContext.before is not Readonly<ItemDocument> so there is no type-level protection.
This is clearly out of scope for the current PR. Would you like me to open a follow-up GitHub issue to track it?
Overall
All nine actionable findings from the review chain are resolved. The PR is in excellent shape and ready to merge from a review standpoint.
🧠 Learnings used
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In `unbraind/pm-cli`, the semantic/hybrid search fallback-on-any-error behavior in `src/cli/commands/search.ts` is intentional. The product requirement is that `pm search` must never block an agent. Every degradation emits a machine-readable `search_<mode>_fallback:<reason>:using_keyword_mode` warning (reason ∈ `timeout`|`connection`|`error`) returned to the caller and captured by telemetry. Sentry was explicitly rejected for this path because it is lazily initialized only in error paths and would impose async init latency on the hot (successful) search path.
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (with extensions `.toon` and `.jsonl`) are generated and managed exclusively by the `pm` CLI tool itself (via `pm create`, `pm close`, `pm update`, etc.). They are never manually edited. Committing these files alongside the work they track is the established convention in this repo (see PRs `#36`–#39). These files should NOT be flagged with "do not edit directly" or similar warnings in code review.
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-22T21:06:50.101Z
Learning: In the pm-cli project, `history-redact`, `restore`, and `history-repair` all follow the same intentional read-compute-lock-write pattern: read history + item document outside the lock → compute the rewritten content → acquire the per-item lock → atomic write with rollback. Tightening the read inside the lock is acknowledged as a future hardening item but is intentionally deferred so the locking model stays consistent across all three sibling commands.
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-28T05:46:05.731Z
Learning: In the `unbraind/pm-cli` repository, `.agents/pm/` files (issues, history, epics, extensions) are the source of truth for the pm CLI tracker and are committed to git as the result of pm CLI mutations (`pm close`, `pm comments`, `pm changelog generate`, etc.). They should not be flagged as hand-edited files in code reviews.
Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-28T06:10:29.890Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (including `.agents/pm/epics/*.toon`, `.agents/pm/extensions/.managed-extensions.json`, `.agents/pm/history/*.jsonl`, and similar) are intentional source-of-truth tracker files for the pm-cli project management system. They are tracked in git by design and should NOT be flagged as files that shouldn't be edited or committed. Review comments suggesting these files should not be modified are false-positives for this repository.
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-30T22:17:32.126Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/**` (e.g. `.agents/pm/issues/`, `.agents/pm/chores/`, `.agents/pm/features/`, `.agents/pm/history/`) are git-tracked by design and are created/updated exclusively via the sanctioned `pm` CLI tooling (`pm create`, `pm close`, `pm comments`, etc.). These files are the repository's internal system of record and should NOT be flagged as "do not edit directly" — direct edits via the `pm` CLI are the correct and intended workflow.
✅ Actions performed
Full review triggered.
There was a problem hiding this comment.
Code Review
This pull request enriches the onWrite hook context with item identity, snapshots, and changed fields across item creation, mutation, deletion, restoration, and import. It also introduces structured validation errors and doctor diagnostics (extension_capability_missing) when an extension registers a capability not declared in its manifest. The review feedback suggests defensively checking that activationFailures is an array in classifyDoctorActivationFailureWarnings to prevent potential runtime errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export function classifyDoctorActivationFailureWarnings( | ||
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [], | ||
| ): string[] { | ||
| const warnings: string[] = []; |
There was a problem hiding this comment.
To prevent potential runtime errors (such as TypeError: activationFailures is not iterable) if activationFailures is explicitly passed as null or a non-array value, we should defensively verify that it is an array before iterating over it.
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [],
): string[] {
if (!Array.isArray(activationFailures)) {
return [];
}
const warnings: string[] = [];|
@codex review Follow-up commit 6a84d6a addresses the latest Gemini defensive request by guarding classifyDoctorActivationFailureWarnings with Array.isArray before iteration. Verification passed: focused extension-command test, pnpm build, and pnpm security:scan. Please re-review the final head for remaining actionable issues. |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
REPO="unbraind/pm-cli"
PR="105"
HEAD_SHA="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.sha')"
HEAD_REF="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.ref')"
echo "PR head ref: $HEAD_REF"
echo "PR head sha: $HEAD_SHA"
echo ""
echo "====== Recent PR commits ======"
gh api "repos/$REPO/pulls/$PR/commits" \
--jq '.[-8:] | .[] | {sha: .sha[0:8], message: (.commit.message | split("\n")[0])}'
echo ""
echo "====== Latest commit files/patches (6a84d6af if present) ======"
gh api "repos/$REPO/commits/6a84d6af" \
--jq '.files[]? | "FILE: \(.filename)\nSTATUS: \(.status)\nPATCH:\n\(.patch // "(no patch)")\n---"'
fetch_file() {
local file="$1"
echo ""
echo "====== $file ======"
gh api "repos/$REPO/contents/$file?ref=$HEAD_SHA" --jq '.content' | base64 -d
}
fetch_file "src/cli/commands/extension/doctor.ts"
fetch_file "src/cli/commands/extension.ts"
fetch_file "tests/unit/extension-command.spec.ts"
fetch_file "tests/unit/package-manifest.spec.ts"
fetch_file "src/sdk/package-import-adapters.ts"
fetch_file "src/core/extensions/extension-types.ts"
fetch_file "docs/SDK.md"
fetch_file "package.json"Length of output: 269207
The latest change in const failures = Array.isArray(activationFailures) ? activationFailures : [];This preserves the normal behavior for valid Also verified from the provided output:
No new actionable issues found in the final pm-cli head. The only previously mentioned item is still the low-priority shared 🧠 Learnings used✅ Action performedFull review finished. |
There was a problem hiding this comment.
Code Review
This pull request introduces several opt-in enhancements to the pm-changelog extension and CLI, including structured JSON output (--changelog-json), alternative release grouping options (--section-by), Conventional-Commits headings (--conventional), contributor lists (--contributors), and history windowing (--limit and --since-version). Additionally, the pm-cli core was updated to enrich the onWrite hook context with item identity and snapshots, and structured traces were added for missing manifest capabilities during activation. The review feedback highlights two potential safety improvements: adding optional chaining when accessing runtimeCreateFieldValues.values in create.ts to prevent a TypeError, and defensively verifying that failure and failure.name are valid in doctor.ts to avoid runtime crashes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [ | ||
| ...Object.keys(registeredItemFieldValues), | ||
| ...Object.keys(runtimeCreateFieldValues.values), | ||
| ]); |
There was a problem hiding this comment.
If runtimeCreateFieldValues is undefined or null, accessing runtimeCreateFieldValues.values will throw a TypeError and crash the command. Consider using optional chaining to safely access values.
| const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [ | |
| ...Object.keys(registeredItemFieldValues), | |
| ...Object.keys(runtimeCreateFieldValues.values), | |
| ]); | |
| const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [ | |
| ...Object.keys(registeredItemFieldValues), | |
| ...(runtimeCreateFieldValues?.values ? Object.keys(runtimeCreateFieldValues.values) : []), | |
| ]); |
| export function classifyDoctorActivationFailureWarnings( | ||
| activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [], | ||
| ): string[] { | ||
| const failures = Array.isArray(activationFailures) ? activationFailures : []; | ||
| const warnings: string[] = []; | ||
| for (const failure of failures) { | ||
| const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability; | ||
| if (typeof missingCapability === "string" && missingCapability.trim().length > 0) { | ||
| warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`); | ||
| } | ||
| } | ||
| return [...new Set(warnings)].sort((left, right) => left.localeCompare(right)); | ||
| } |
There was a problem hiding this comment.
To prevent potential runtime crashes in the doctor command when processing malformed or incomplete activation failures, defensively verify that failure is defined and failure.name is a valid string before accessing its properties.
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [],
): string[] {
const failures = Array.isArray(activationFailures) ? activationFailures : [];
const warnings: string[] = [];
for (const failure of failures) {
if (!failure || typeof failure.name !== "string") {
continue;
}
const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push("extension_capability_missing:" + failure.name + ":" + missingCapability.trim().toLowerCase());
}
}
return [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
}There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a84d6af65
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (limit !== undefined && result.length > limit) { | ||
| result = result.slice(0, limit); |
There was a problem hiding this comment.
Preserve Unreleased outside changelog limits
When --all-release-tags --limit N is used and the leading Unreleased section has items, this slices the full section list, so --limit 1 returns only Unreleased and drops the most recent released section. The flag and nearby comment say the limit applies to release-window sections while keeping Unreleased, so users trying to keep pending notes plus the latest N releases will generate an incomplete changelog.
Useful? React with 👍 / 👎.
| throw createRegistrationValidationError( | ||
| `${method} requires capability '${capability}' in extension manifest capabilities`, | ||
| { | ||
| method, | ||
| registration_index: -1, | ||
| capability, | ||
| missing_capability: capability, |
There was a problem hiding this comment.
Preserve nested missing-capability traces
When an extension declares commands but omits schema and uses registerCommand({ ..., flags: [...] }), this missing-capability error is thrown from inside the command metadata validation block and then rewrapped by registerCommand's catch without missing_capability. In that scenario doctor still reports an activation failure, but the new extension_capability_missing triage warning and --trace fields are lost, so the actionable diagnostic added here does not work for inline command flags.
Useful? React with 👍 / 👎.
| if (booleanOption(ctx.options, "changelog-json", "changelogJson")) { | ||
| const document = buildChangelogDocument(generationOptions); | ||
| return { document, format: "json", item_count: document.item_count }; |
There was a problem hiding this comment.
Emit real JSON for the extension changelog-json flag
For the pm extension command, pm changelog generate --changelog-json returns a normal object, so the default pm output path renders it as TOON unless the caller also knows to pass the global --json. The standalone CLI writes JSON directly, and the docs advertise the same --changelog-json flag for downstream tooling, so redirecting this extension command produces a non-JSON changelog document.
Useful? React with 👍 / 👎.
| const names = collectContributors(section.items); | ||
| if (names.length > 0) { | ||
| lines.push("### Contributors", ""); | ||
| lines.push(names.map((name) => `@${name}`).join(", ")); |
There was a problem hiding this comment.
Avoid double-prefixing contributor handles
When --contributors is enabled and an item's assignee or author is already stored as a handle such as @alice, this rendering unconditionally prepends another @, producing @@alice in the changelog. That makes contributor sections wrong for the common handle format; normalize the collected name or only add the prefix when it is absent.
Useful? React with 👍 / 👎.
|
@codex review Follow-up commit 84b5ef7 addresses the latest Gemini defensive suggestions by tolerating absent runtimeCreateFieldValues.values and skipping malformed activation failure entries before reading failure.name. Verification passed focused tests, pnpm build, and pnpm security:scan. Please re-review the final head for remaining actionable issues. |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
REPO="unbraind/pm-cli"
PR="105"
echo "====== PR head ======"
if command -v gh >/dev/null 2>&1; then
HEAD_SHA="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.sha')"
HEAD_REF="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.ref')"
echo "Head ref: $HEAD_REF"
echo "Head sha: $HEAD_SHA"
echo ""
echo "====== Recent PR commits ======"
gh api "repos/$REPO/pulls/$PR/commits" \
--jq '.[-10:] | .[] | {sha: .sha[0:8], message: (.commit.message | split("\n")[0])}'
echo ""
echo "====== Latest commit patch: 84b5ef70 ======"
gh api "repos/$REPO/commits/84b5ef70" \
--jq '.files[]? | "FILE: \(.filename)\nSTATUS: \(.status)\nPATCH:\n\(.patch // "(no patch)")\n---"' || true
fi
echo ""
echo "====== Repository files available? ======"
pwd
fd -H 'doctor\.ts|extension\.ts|create\.ts|package-manifest\.spec\.ts|extension-command\.spec\.ts|item-store\.spec\.ts|package-import-adapters\.ts|extension-types\.ts|SDK\.md|package\.json' . | sort
echo ""
echo "====== Search: classifyDoctorActivationFailureWarnings ======"
rg -n -C 8 'classifyDoctorActivationFailureWarnings|extension_capability_missing|missing_capability|activationFailures|failure\.name' .
echo ""
echo "====== Search: runtimeCreateFieldValues ======"
rg -n -C 8 'runtimeCreateFieldValues|values' .
echo ""
echo "====== Relevant files from checkout, if present ======"
for file in \
"src/cli/commands/extension/doctor.ts" \
"src/cli/commands/extension.ts" \
"src/cli/commands/create.ts" \
"tests/unit/extension-command.spec.ts" \
"tests/unit/package-manifest.spec.ts" \
"src/sdk/package-import-adapters.ts" \
"src/core/extensions/extension-types.ts" \
"docs/SDK.md" \
"package.json"
do
if [ -f "$file" ]; then
echo ""
echo "====== $file ======"
sed -n '1,260p' "$file"
fi
doneLength of output: 277536
The latest defensive changes look correct:
No remaining actionable issues found in the final pm-cli head. The SDK hook context, manifest capability diagnostics, package doctor guardrails, and package-first dogfood checks all look merge-ready from this review pass. 🧠 Learnings used✅ Action performedFull review finished. |
There was a problem hiding this comment.
Code Review
This pull request introduces several opt-in enhancements to the pm-changelog extension, including structured JSON document output, within-release grouping, Conventional-Commits heading mapping, contributor lists, and release history windowing. Additionally, it enriches the SDK's onWrite hook context with item identity, snapshots, and changed fields across item mutations, and adds robust manifest capability guardrails and doctor diagnostics to identify missing capabilities during extension activation. As there are no review comments provided, I have no feedback to offer on the changes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 84b5ef7041
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }); | ||
| } | ||
| if (limit !== undefined && result.length > limit) { | ||
| result = result.slice(0, limit); |
There was a problem hiding this comment.
Preserve the requested release count when limiting
When --all-release-tags --limit N runs while there are pending Unreleased items, resolveReleaseTagWindows prepends the Unreleased window before tagged releases, so this slice counts that window against the limit. For example --limit 1 emits only Unreleased and drops the newest tagged release, even though the new option is documented as keeping the newest N releases and the comment above says Unreleased is always kept. The limit needs to apply to tagged release windows separately from the leading Unreleased section.
Useful? React with 👍 / 👎.
| } | ||
| if (!declared.has(capability)) { | ||
| throw new TypeError( | ||
| throw createRegistrationValidationError( |
There was a problem hiding this comment.
Preserve missing-capability traces from nested validation
When an extension has commands but omits schema and calls registerCommand({ ..., flags }), this traced capability error is thrown from inside the surrounding registerCommand metadata try, then immediately caught and rethrown with a fresh registerCommandTrace that drops missing_capability. In that scenario activation still fails, but doctor cannot emit the new extension_capability_missing:<name>:schema remediation promised by the docs; preserve the existing registration-validation trace when wrapping nested metadata errors.
Useful? React with 👍 / 👎.
| } | ||
| else { | ||
| for (const tag of tags) | ||
| push(tag, item); |
There was a problem hiding this comment.
Sanitize label headings before rendering
With the new --section-by label mode, tag values from --input/--stdin or hand-edited tracker data are copied directly into ### headings. If a tag contains a newline or Markdown heading text, the generated changelog can be structurally corrupted by an injected section even though item titles are escaped; normalize label headings the same way as type/status headings or escape them before rendering.
Useful? React with 👍 / 👎.
Summary
onWritehook contexts withitem_id,item_type,before,after, andchanged_fieldsfor create/update/delete/restore/import writes while preserving existing minimal non-item write events.extension_capability_missingdoctor remediation so package authors can fix manifest/API drift before publishing.commanderto 15.0.0, refresh managedpm-changelogto 2026.6.3, and regenerateCHANGELOG.md.Fixes #103.
Fixes #104.
Tracker:
pm-e9utVerification
pnpm buildnode scripts/run-tests.mjs test -- tests/unit/contracts-command.spec.ts tests/unit/help-runtime.spec.ts tests/unit/config-positional-value.spec.ts tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.tsnode scripts/run-tests.mjs test -- tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.tsnpm run dogfood:package-firstpnpm audit --audit-level moderate && pnpm outdated --format jsonpnpm security:scanpnpm changelog:pm:checkNotes
26870058549failed before release gates becauseSENTRY_PERSONAL_ADMIN_TOKENis not configured for the scheduled push path; this is separate from the SDK/package fixes here.pm-changelog@2026.6.3is the latest available package and owns the generated changelog path.